package org.kwin.ibatis.proxy;

import org.kwin.ibatis.annotation.*;
import org.kwin.ibatis.session.BaseMapper;
import org.kwin.ibatis.session.SqlSession;
import org.kwin.ibatis.session.SqlSessionDefultUtil;
import org.kwin.ibatis.util.MySQLUtil;
import org.kwin.ibatis.util.TypeUtil;

import java.io.Serializable;
import java.lang.reflect.*;
import java.sql.*;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 * @author : wangkai
 * @version V1.0
 * @Package org.kwin.ibatis.proxy
 * @Description:
 * @date 2021年10月09日 13:40
 **/
public class AnalyticMethod {
    
    // 事务的conn 位置
    public static final ThreadLocal<SqlSession> sqlSession = new ThreadLocal<>();
    
    // 获取sqlSession
    protected static Object invoke(Class clzz, Object targetObject, Method method, Object[] args) {
        if(objectMethod(method)) {
            return targetObject.getClass().getName();
        }
        
        SqlSession sqlSess = sqlSession.get();
        Connection connection = null;
        if(sqlSess == null) {
            throw new RuntimeException("connection not exist");
        } else {
            connection = sqlSess.getConnection();
        }
    
        if(baseSql(method)) {
            return baseExec(clzz, targetObject, method, connection, args);
        }
    
        Object restlt = null;
        StringBuilder sql = new StringBuilder();
        if (method.isAnnotationPresent(Select.class))
        {
            restlt = exeSelect(method, args, sql, connection);
        } else if (method.isAnnotationPresent(Insert.class) || method.isAnnotationPresent(Update.class) || method.isAnnotationPresent(
            Delete.class))
        {
            restlt = exeUpdate(method, args, sql, connection);
        }
    
        Class<?> resultType = method.getReturnType();
        if(resultType == Void.TYPE) {
            return null;
        } else if (TypeUtil.isComposite(resultType))
        {
            return restlt;
        } else if (TypeUtil.isString(resultType))
        {
            
            return restlt == null ? null : restlt.toString();
        }
        if (resultType == int.class || resultType == Integer.class)
        {
            return restlt == null ? null : Integer.parseInt(restlt.toString());
        } else if (resultType == long.class || resultType == Long.class)
        {
            return restlt == null ? null : Long.parseLong(restlt.toString());
        } else if (resultType == byte.class || resultType == Byte.class)
        {
            return restlt == null ? null : Byte.parseByte(restlt.toString());
        } else if (resultType == short.class || resultType == Short.class)
        {
            return restlt == null ? null : Byte.parseByte(restlt.toString());
        }
        return null;
    }
    
    private static boolean objectMethod(Method method) {
        Method[] methods = Object.class.getDeclaredMethods();
        // TODO:
        for(Method meth : methods) {
            if(meth.equals(method)) {
                return true;
            }
        }
        return false;
    }
    
    private static boolean baseSql(Method method) {
        Method[] methods = BaseMapper.class.getDeclaredMethods();
        // TODO:
        for(Method meth : methods) {
            if(meth.equals(method)) {
                return true;
            }
        }
        return false;
    }
    
    
    
    
    
    
    
    public static Class<?> getInterfaceT(Class clzz, int index) {
        Type[] types = clzz.getGenericInterfaces();
        ParameterizedType parameterizedType = (ParameterizedType) types[index];
        Type type = parameterizedType.getActualTypeArguments()[index];
        return checkType(type, index);
        
    }
    
    
    /**
     * 获取类上的泛型T
     *
     * @param o     接口
     * @param index 泛型索引
     */
    public static Class<?> getClassT(Object o, int index) {
        Type type = o.getClass().getGenericSuperclass();
        if (type instanceof ParameterizedType) {
            ParameterizedType parameterizedType = (ParameterizedType) type;
            Type actType = parameterizedType.getActualTypeArguments()[index];
            return checkType(actType, index);
        } else {
            String className = type == null ? "null" : type.getClass().getName();
            throw new IllegalArgumentException("Expected a Class, ParameterizedType"
                + ", but <" + type + "> is of type " + className);
        }
    }
    
    private static Class<?> checkType(Type type, int index) {
        if (type instanceof Class<?>) {
            return (Class<?>) type;
        } else if (type instanceof ParameterizedType) {
            ParameterizedType pt = (ParameterizedType) type;
            Type t = pt.getActualTypeArguments()[index];
            return checkType(t, index);
        } else {
            String className = type == null ? "null" : type.getClass().getName();
            throw new IllegalArgumentException("Expected a Class, ParameterizedType"
                + ", but <" + type + "> is of type " + className);
        }
    }
    
    private static Object baseExec(Class clazz, Object targetObject, Method method, Connection conn, Object[] args) {
        String name = method.getName();
        System.out.println("base method " + name);
        if( "saveOrUpdate".equals(name)) {
            return SqlSessionDefultUtil.saveOrUpdate(conn, args[0]);
        }
    
        if( "save".equals(name)) {
            return SqlSessionDefultUtil.save(conn, args[0]);
        }
    
        if( "update".equals(name)) {
            return SqlSessionDefultUtil.update(conn, args[0]);
        }
//        System.out.println(method.getDeclaringClass().getName());
//        System.out.println(method.getDeclaringClass().getGenericInterfaces()[0]);
        Class clzz = getInterfaceT(clazz,0);
        if( "getAll".equals(name)) {
            return SqlSessionDefultUtil.getAll(conn, clzz);
        }
        
        if("getById".equals(name)) {
            return SqlSessionDefultUtil.getById(conn, clzz, (Serializable)args[0]);
        }
    
        if( "delete".equals(name)) {
            return SqlSessionDefultUtil.delete(conn, clzz, (Serializable)args[0]);
        }
    
        if( "getAllByPage".equals(name)) {
            return SqlSessionDefultUtil.getAllByPage(conn, clzz, Integer.valueOf(args[0].toString()), Integer.valueOf(args[1].toString()));
        }
    
        if( "getCount".equals(name)) {
            return SqlSessionDefultUtil.getCount(conn);
        }
    
        if( "close".equals(name)) {
            MySQLUtil.closeAll(conn, null, null);
            return null;
        }
    
        if( "flush".equals(name)) {
            MySQLUtil.flush(conn);
            return null;
        }
        return null;
    }
    
    private static Object exeUpdate(Method method, Object[] args, StringBuilder sql, Connection connection)
    {
        String value;
        if (method.isAnnotationPresent(Insert.class))
        {
            Insert myInsert = method.getAnnotation(Insert.class);
            value = myInsert.value();
        } else if (method.isAnnotationPresent(Delete.class))
        {
            Delete myInsert = method.getAnnotation(Delete.class);
            value = myInsert.value();
        } else
        {
            Update myInsert = method.getAnnotation(Update.class);
            value = myInsert.value();
        }
        sql.append(value);
        initParameter(method.getParameters(), sql, args);
        System.out.println("执行SQL语句：" + sql);
        PreparedStatement ps = null;
        try
        {
            ps = connection.prepareStatement(sql.toString());
            return ps.executeUpdate();
        } catch (Exception e)
        {
            e.printStackTrace();
        } finally
        {
            MySQLUtil.closeAll(null, ps, null);
        }
        return null;
    }
    
    private static Object exeSelect(Method method, Object[] args, StringBuilder sql, Connection connection)
    {
        Select mySelect = method.getAnnotation(Select.class);
        sql.append(mySelect.value());
        Parameter[] parameters = method.getParameters();
        initParameter(parameters, sql, args);
        System.out.println("执行SQL语句：" + sql);
        PreparedStatement ps = null;
        ResultSet rs = null;
        try
        {
            ps = connection.prepareStatement(sql.toString());
            rs = ps.executeQuery();
            Type type = method.getGenericReturnType();
            Class clazz = method.getReturnType();
            if (clazz.isInterface())
            {
                if (type instanceof ParameterizedType)
                {
                    Type[] types = ((ParameterizedType) type).getActualTypeArguments();
                    Type t = types[0];
                    return MySQLUtil.getListByResult(rs, Class.forName(t.getTypeName()));
                }
            } else
            {
                List list=MySQLUtil.getListByResult(rs, clazz);
                if (list.size() == 0)
                {
                    return null;
                } else if(list.size()==1)
                {
                    return list.get(0);
                }else
                {
                    return list;
                }
            }
        } catch (Exception e)
        {
            e.printStackTrace();
        } finally
        {
            MySQLUtil.closeAll(null, ps, rs);
        }
        return null;
    }
    
    private static void initParameter(Parameter[] parameters, StringBuilder sql, Object[] args)
    {
        if (args == null)
        {
            return;
        }
        for (int i = 0; i < args.length; i++)
        {
            if (parameters[i].isAnnotationPresent(Param.class))
            {
                Param myParam = parameters[i].getAnnotation(Param.class);
                String value = myParam.value();
                String temp = null;
                if (TypeUtil.isComposite(parameters[i].getType()))
                {
                    Object o = args[i];
                    Map<String, Object> map = new HashMap<>();
                    Field[] fields = o.getClass().getDeclaredFields();
                    try
                    {
                        for (Field f : fields)
                        {
                            f.setAccessible(true);
                            if (f.get(o) != null)
                            {
                                map.put(f.getName(), f.get(o));
                            } else
                            {
                                if (TypeUtil.isNumber(f.getType()))
                                {
                                    map.put(f.getName(), 0);
                                } else
                                {
                                    map.put(f.getName(), "");
                                }
                            }
                        }
                    } catch (Exception e)
                    {
                        e.printStackTrace();
                    }
                    for (String key : map.keySet())
                    {
                        if (map.get(key) == null)
                        {
                            continue;
                        }
                        if (TypeUtil.isNumber(map.get(key).getClass()))
                        {
                            temp = sql.toString().replace("#{" + value + "." + key + "}", map.get(key).toString());
                        } else
                        {
                            temp = sql.toString().replace("#{" + value + "." + key + "}", "'" + map.get(key) + "'");
                        }
                        sql = sql.delete(0, sql.length()).append(temp);
                    }
                    System.out.println("map=" + map);
                } else if (TypeUtil.isNumber(parameters[i].getType()))
                {
                    temp = sql.toString().replace("#{" + value + "}", args[i].toString());
                } else
                {
                    temp = sql.toString().replace("#{" + value + "}", "'" + args[i].toString() + "'");
                }
                sql.delete(0, sql.length()).append(temp);
            }
        }
    }
}
